Xóa phụ huynh nếu nó không được tham chiếu bởi bất kỳ đứa trẻ nào khác (Delete parent if it's not referenced by any other child)


問題描述

Xóa phụ huynh nếu nó không được tham chiếu bởi bất kỳ đứa trẻ nào khác (Delete parent if it's not referenced by any other child)

I have an example situation: parent table has a column named id, referenced in child table as a foreign key.

When deleting a child row, how to delete the parent as well if it's not referenced by any other child?


參考解法

方法 1:

In PostgreSQL 9.1 or later you can do this with a single statement using a data‑modifying CTE. This is generally less error prone. It minimizes the time frame between the two DELETEs in which a race conditions could lead to surprising results with concurrent operations:

<pre class="lang‑sql prettyprint‑override">WITH del_child AS (     DELETE FROM child     WHERE  child_id = 1     RETURNING parent_id, child_id     ) DELETE FROM parent p USING  del_child x WHERE  p.parent_id = x.parent_id AND    NOT EXISTS (    SELECT 1    FROM   child c    WHERE  c.parent_id = x.parent_id    AND    c.child_id <> x.child_id   ‑‑ !    ); </pre>

db<>fiddle here Old sqlfiddle

The child is deleted in any case. I quote the manual:

Data‑modifying statements in WITH are executed exactly once, and always to completion, independently of whether the primary query reads all (or indeed any) of their output. Notice that this is different from the rule for SELECT in WITH: as stated in the previous section, execution of a SELECT is carried only as far as the primary query demands its output.

The parent is only deleted if it has no other children. Note the last condition. Contrary to what one might expect, this is necessary, since:

The sub‑statements in WITH are executed concurrently with each other and with the main query. Therefore, when using data‑modifying statements in WITH, the order in which the specified updates actually happen is unpredictable. All the statements are executed with the same snapshot (see Chapter 13), so they cannot "see" each others' effects on the target tables.

Bold emphasis mine. I used the column name parent_id in place of the non‑descriptive id.

Eliminate race condition

To eliminate possible race conditions I mentioned above completely, lock the parent row first. Of course, all similar operations must follow the same procedure to make it work.

<pre class="lang‑sql prettyprint‑override">WITH lock_parent AS (    SELECT p.parent_id, c.child_id    FROM   child  c    JOIN   parent p ON p.parent_id = c.parent_id    WHERE  c.child_id = 12              ‑‑ provide child_id here once    FOR    NO KEY UPDATE                ‑‑ locks parent row.    )  , del_child AS (    DELETE FROM child c    USING  lock_parent l    WHERE  c.child_id = l.child_id    ) DELETE FROM parent p USING  lock_parent l WHERE  p.parent_id = l.parent_id AND    NOT EXISTS (    SELECT 1    FROM   child c    WHERE  c.parent_id = l.parent_id    AND    c.child_id <> l.child_id   ‑‑ !    ); </pre>

This way only one transaction at a time can lock the same parent. So it cannot happen that multiple transactions delete children of the same parent, still see other children and spare the parent, while all of the children are gone afterwards. (Updates on non‑key columns are still allowed with FOR NO KEY UPDATE.)

If such cases never occur or you can live with it (hardly ever) happening ‑ the first query is cheaper. Else, this is the secure path.

FOR NO KEY UPDATE was introduced with Postgres 9.4. Details in the manual. In older versions use the stronger lock FOR UPDATE instead.

方法 2:

delete from child
where parent_id = 1

After deleted in the child do it in the parent:

delete from parent
where
    id = 1
    and not exists (
        select 1 from child where parent_id = 1
    )

The not exists condition will make sure it will only be deleted if it does not exist in the child. You can wrap both delete commands in a transaction:

begin;
first_delete;
second_delete;
commit;

(by zeroDivisibleErwin BrandstetterClodoaldo Neto)

參考文件

  1. Delete parent if it's not referenced by any other child (CC BY‑SA 3.0/4.0)

#referential-integrity #SQL #common-table-expression #foreign-keys #postgresql






相關問題

Jika saya memiliki batasan kunci asing dari tabel itu sendiri, apakah saya perlu berhati-hati saat menghapus seluruh tabel? (If I have a foreign key constraint of a table to itself, do I need to be careful when deleting the whole table?)

如何檢查我是否只刪除了所需的數據? (How do I check that I removed required data only?)

如何在 Postgres 8.2 中禁用參照完整性? (How do I disable referential integrity in Postgres 8.2?)

Xóa phụ huynh nếu nó không được tham chiếu bởi bất kỳ đứa trẻ nào khác (Delete parent if it's not referenced by any other child)

Có cách nào để kiểm tra tính toàn vẹn của tham chiếu cho các bảng MyIsam bằng cách sử dụng quan hệ gốc YII không? (Is there a way to check referential integrity for MyIsam tables using YII native relations?)

ActiveDirectoryMembershipProvider 和參照完整性 (ActiveDirectoryMembershipProvider and referential integrity)

SQL2005:將一個錶鍊接到多個表並保留Ref Integrity? (SQL2005: Linking a table to multiple tables and retaining Ref Integrity?)

違反完整性約束 - 調用存儲過程時未找到父鍵 (Integrity constraint violated - parent key not found when calling stored procedure)

db2 參照完整性問題 (db2 referential integrity problem)

無法在 Access 中強制執行參照完整性 (unable to enforce referential integrity in Access)

破壞的參照完整性:埃德加科德會說什麼? (Broken referential integrity: What would Edgar Codd say?)

如何更新鏈接到多個表的 FK - 更新時的級聯 (How to update FK linked to multiple table - Cascade on Update)







留言討論